package com.sixbro.authorization.config.redis;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.StringJoiner;

import static cn.hutool.core.date.DatePattern.*;
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair.fromSerializer;

/**
 * <p>
 *  Redis 缓存配置
 * </p>
 *
 * @author: Mr.Lu
 * @since: 2021/6/15 16:45
 */
@Slf4j
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {

    public static final int DEFAULT_EXPIRE_TIME = 60;

    /**
     * @description TODO 缓存key前缀
     */
    private static final String keyPrefix = "CACHE:";

    @Resource
    private RedisConnectionFactory factory;

    /**
     * 自定义缓存管理
     */
    @Bean
    @Override
    public RedisCacheManager cacheManager() {
        return RedisCacheManager.builder()
                .cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(factory))
                // 更新删除上锁
                .transactionAware()
                // 默认缓存策略
                .cacheDefaults(redisCacheConfigurationEntryTtl(DEFAULT_EXPIRE_TIME, ChronoUnit.SECONDS))
                .build();
    }

    /**
     * 配置redis的cache策略
     *
     * @param time
     * @param timeUnit
     * @return
     */
    private RedisCacheConfiguration redisCacheConfigurationEntryTtl(long time, TemporalUnit timeUnit) {
        return RedisCacheConfiguration.defaultCacheConfig()
                // 设置cache的过期策略
                .entryTtl(Duration.of(time, timeUnit))
                // 设置key的序列化
                .serializeKeysWith(fromSerializer(keySerializer()))
                // 设置value的序列化
                .serializeValuesWith(fromSerializer(valueSerializer()))
                // 不缓存null的值
                .disableCachingNullValues();
    }

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }


    /**
     * org.springframework.cache.interceptor包下的
     * 自定义key的生成策略,对应@Cacheable中的keyGenerator
     * 实例对象+方法名+参数名
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
//                StringBuffer sb = new StringBuffer();
//                sb.append(target.getClass().getName());
//                sb.append(":");
//                sb.append(method.getName());
//                for (Object obj : params) {
//                    sb.append(":").append(obj.toString());
//                }
//                return sb.toString();

                StringJoiner joiner = new StringJoiner(":", keyPrefix,"");
                joiner.add(target.getClass().getSimpleName());
                joiner.add(method.getName());

                for (Object param : params) {
                    joiner.add(JSONObject.toJSONString(param));
                }
                return joiner.toString();
            }
        };
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }

    /**
     * 配置自定义 redisTemplate<String, Object>
     *
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // key 采用 string 的序列化方式
        template.setKeySerializer(keySerializer());
        // hash 的 key 采用 string 的序列化方式
        template.setHashKeySerializer(keySerializer());
        // value 采用 jackson 的序列化方式
        template.setValueSerializer(valueSerializer());
        // hash 的 value 采用 jackson 的序列化方式
        template.setHashValueSerializer(valueSerializer());
        template.afterPropertiesSet();

        return template;
    }

    /**
     * key采用序列化策略
     */
    private RedisSerializer<String> keySerializer() {
        return RedisSerializer.string();
    }

    /**
     * value采用序列化策略
     */
    private RedisSerializer<Object> valueSerializer() {
        // 序列化所有类包括jdk提供的
        ObjectMapper objectMapper = new ObjectMapper();
        // 设置序列化的域(属性,方法etc)以及修饰范围,Any包括private,public 默认是public的
        // ALL所有方位,ANY所有修饰符
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // enableDefaultTyping 原来的方法存在漏洞,2.0后改用如下配置
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 指定序列化输入的类型，类必须是非final修饰的类
        // objectMapper.activateDefaultTyping(BasicPolymorphicTypeValidator.builder().build(), ObjectMapper.DefaultTyping.EVERYTHING);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        // 不转换值为 null 的对象
        // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 下面代码解决LocalDateTime序列化与反序列化不一致问题
        // objectMapper.setDateFormat(new SimpleDateFormat(NORM_DATETIME_MS.getValue()));
        // objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 如果java.time包下Json报错,添加如下两行代码, 下面配置解决LocalDateTime序列化的问题
        // objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // LocalDateTime系列序列化和反序列化模块，继承自jsr310，我们在这里修改了日期格式
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        // 日期序列化
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(NORM_DATETIME_FORMATTER));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(NORM_DATE_FORMATTER));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(NORM_TIME_FORMATTER));

        // 日期反序列化
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(NORM_DATETIME_FORMATTER));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(NORM_DATE_FORMATTER));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(NORM_TIME_FORMATTER));

        objectMapper.registerModule(javaTimeModule);
        // objectMapper.registerModule(new ParameterNamesModule());
        // objectMapper.registerModule(new Jdk8Module());

        RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(objectMapper);

        return serializer;
    }

    @Bean
    @ConditionalOnBean(name = "redisTemplate")
    public RedisService redisService() {
        return new RedisService(redisTemplate());
    }

}